'''A private module containing a parser and processors for BibTeX documents.
Used by the L{Bibliography} module.

@copyright: (c) 2010, Benjamin Kalish

@license:
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful, but
    WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    General Public License for more details.
'''

# TO DO: Redo all docs in this file
# TO DO: Move all processors to this file

from simpleparse.parser import Parser
from simpleparse.common import chartypes
from simpleparse.dispatchprocessor import *

_declaration = r'''
# BE CAREFUL WHEN EDITING THIS GRAMMAR - simpleparse has limited suport for
# backtracking and constructions such as 'a := b*, b' will not parse.

<eof>             := EOF
<ws>              := whitespace
escaped_chars     := '\\"' / '\\{' / '\\}'

brace_string      := '{', (no_brace_string / brace_string)* , '}'
<no_brace_string> := (escaped_chars / -[{}])+

top               := document, (eof / BREAK)!
document          := (bibtexcomment?, ?'@',command)*, bibtexcomment?
command           := scribecomment / stringdef / preamble / bibentry!
stringdef         := c'@string', ws?, open_brace, ws?, field_name, equal_op, string, ws?, ','?, close_brace
preamble          := c'@preamble', ws?, open_brace, ws?, string, ws?, close_brace
bibentry          := bibtype!, ws?, open_brace, ws?, citekey!, ws?, required_comma, fields, ws?, close_brace
bibtype           := '@', alphanums
citekey           := safe_characters+
fields            := field, (',', field)*, ','?
field             := ws?, field_name, equal_op, field_value, ws?
field_name        := letter, safe_characters*
field_value       := (string / number) ! "field_value missing around line %(line)s"
string            := (string_var / string_lit), (concat_op, string)*
string_var        := letter, safe_characters*
string_lit        := quote_string / brace_string
quote_string      := open_quote, (escaped_chars / -'"')*, close_quote
<scribecomment>   := c'@comment', ws?, open_brace , bibtexcomment
<bibtexcomment>   := -(BREAK / '@')+

number            := [0-9]+
<equal_op>        := ws?, required_equal, ws?
<concat_op>       := ws?, '#', ws?

<safe_characters> := [][a-zA-Z0-9_+:.|?/<>!%^&*()-]
<alphanums>       := [a-zA-Z0-9]+

<required_equal>  := '='! "expected an '=' around line %(line)s of of bibtex data\nfound: %(text)s"
<required_comma>  := ','! "expected a ',' around line %(line)s of of bibtex data\nfound: %(text)s"
<open_brace>      := '{'! "expected a '{' around line %(line)s of of bibtex data\nfound: %(text)s"
<close_brace>     := '}'! "could not parse bibtex data, perhaps because of a missing comma or mismatched brace\nfound: %(text)s"
<open_quote>      := '"'
<close_quote>     := '"'! "the supplied bibtex data appears to be missing a closing quotation mark\nfound: %(text)s"

<BREAK>      := '--BREAK--'

'''

class BibtexProcessor( DispatchProcessor ):
    '''A subclass of simpleparse.DispatchProcessor for processing
    BibtexParser result trees.
    
    BibtexProcessor will be used by default when you call parse() on a
    BibtexParser object.
    '''
    
    entries = []
    string_defs = {}
    preamble_string = ''
    
    def __init__(self):
       # Ensure that each BibtexProcessor begins with a clean slate.
        self.entries = []
        self.string_defs = {}
        self.preamble_string = ''

    def __call__(self, value, buffer):
        '''Ensures that a bibliography is returned directly when 
        BibtexProcessor is used as a base processor.'''
        if len(value) == 3:
            (success, tags, next) = value
            return dispatchList(self, tags, buffer)[0]
        else:
			return dispatch( self, value, buffer )
        
    def document( self, (tag,start,stop,subtags), buffer ):
        '''Process entire document and return a dictionary with the
        results.

        The results dictionary has the following keys and field_values:
        
           - 'entries': a list of entries corresponding to bibtex entries

           - 'string_defs': a dictionary of strings defined using
           @string

           - 'preamble': a string defined using @preamble
        '''
        for item in subtags:
            dispatch( self, item, buffer)
        return {
            'entries': self.entries,
            'string_defs' : self.string_defs,
            'preamble' : self.preamble_string}
        
    def command( self, (tag,start,stop,subtags), buffer ):
        '''Process a command. Delegate processing of subtags to their 
        respective handlers.'''
        for item in subtags:
            dispatch( self, item, buffer)
        
    def stringdef( self, (tag,start,stop,subtags), buffer ):
        '''Processes @string defintions and updates string_defs
        accordingly.'''
        set = {}
        for item in subtags:
            set[ item[0] ] = dispatch( self, item, buffer)
        self.string_defs[set['field_name'].lower()] = set['string']       
    
    def preamble( self, (tag,start,stop,subtags), buffer ):
        '''Process @preamble and adds text to preamble string.'''
        set = {}
        for item in subtags:
            set[ item[0] ] = dispatch( self, item, buffer)
        self.preamble_string += set['string']    

    def bibentry( self, (tag,start,stop,subtags), buffer ):
        '''Process a bibentry and add resulting dictionary to
        entries.'''
        dict = {}
        for item in subtags:
            dict[ item[0] ] = dispatch( self, item, buffer)
        entry = (dict['bibtype'], dict['citekey'], dict['fields'])
        self.entries.append(entry)

    def bibtype( self, (tag,start,stop,subtags), buffer ):
        '''Process a bibtype and return it, minus the initial '@'.'''
        return getString( (tag,start,stop,subtags), buffer)[1:]
        
    def citekey( self, (tag,start,stop,subtags), buffer ):
        '''Process a citekey and return it.'''
        return getString( (tag,start,stop,subtags), buffer)
        
    def fields( self, (tag,start,stop,subtags), buffer ):
        '''Process a set of consectuive fields and return a dict built
        from their names and field_values.'''
        dict = {}
        for item in subtags:
            if item[0] == 'field':
                (key, field_value) = dispatch( self, item, buffer)
                dict[key] = field_value
        return dict
        
    def field( self, (tag,start,stop,subtags), buffer ):
        '''Process a field and return a tuple with its name and
        field_value.'''
        set = {}
        for item in subtags:
            set[ item[0] ] = dispatch( self, item, buffer)
        return (set['field_name'], set['field_value'])
        
    def field_name( self, (tag,start,stop,subtags), buffer ):
        '''Process and return a name.'''
        return getString( (tag,start,stop,subtags), buffer)
        
    def field_value( self, (tag,start,stop,subtags), buffer ):
        '''Process and return a field_value.'''
        return dispatch( self, subtags[0], buffer)
        
    def string( self, (tag,start,stop,subtags), buffer ):
        '''Process and return a string.'''
        s = ''
        for item in subtags:
            if item[0] != 'concat_op':
                s += dispatch( self, item, buffer)
        return s
         
    def string_var( self, (tag,start,stop,subtags), buffer ):
        '''Returns the result of string substitution.'''
        var_name = getString( (tag,start,stop,subtags), buffer)
        return self.string_defs[var_name.lower()]
        
    def string_lit( self, (tag,start,stop,subtags), buffer ):
        '''Process and return a string literal.'''
        return dispatch( self, subtags[0], buffer)
        
    def quote_string( self, (tag,start,stop,subtags), buffer ):
        '''Process and return a quoted string.'''
        return getString( (tag,start,stop,subtags), buffer)[1:-1]
        
    def brace_string( self, (tag,start,stop,subtags), buffer ):
        '''Process and return a string enclosed in braces.'''
        return getString( (tag,start,stop,subtags), buffer)[1:-1]
                
    def number( self, (tag,start,stop,subtags), buffer ):
        '''Process and return a number.'''
        return getString( (tag,start,stop,subtags), buffer)

class BibtexParser( Parser ):
    '''A subclass of simpleparse.Parser for parseing and processing
    BibTeX data.

    BibtexParser uses BibtexProcessor as its default processor and 'top'
    as its default production, but these can be changed by passing the
    appropriate arguments to parse(). See the simpleparse documentation
    for more information.
    '''
    def __init__(self):
        # Loads the bibtex grammar.
        Parser.__init__(self, _declaration)
        
    def buildProcessor( self ):
        '''Sets the default Processor.'''
        return BibtexProcessor()

    def parse(self, data, production=None, processor=None, start=0, stop=None):
        '''Parses and processes a bibtex file and returns the result.'''
        if production==None:
            production = 'top'
        return Parser.parse(self, data, production, processor, start, stop)
        
BIBTEX_PARSER = BibtexParser()
'''A BibtexParser object ready for use.'''